/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年6月1日
 */
package com.massyframework.beanskin.runtime.framework.installing.plugin;

import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.massyframework.beanskin.Module;
import com.massyframework.beanskin.ModuleIdentifier;
import com.massyframework.beanskin.ModuleJarsLocator;
import com.massyframework.beanskin.ModuleRepository;
import com.massyframework.beanskin.ModuleRuntimeException;
import com.massyframework.beanskin.RequireLibraryDescriptor;
import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.framework.Framework;
import com.massyframework.beanskin.plugin.Plugin;
import com.massyframework.beanskin.plugin.PluginActivator;
import com.massyframework.beanskin.plugin.PluginBuilder;
import com.massyframework.beanskin.runtime.ModuleRegistry;
import com.massyframework.beanskin.runtime.framework.installing.DefaultEnvironment;
import com.massyframework.beanskin.runtime.framework.installing.ModuleClassLoaderBuilderFactory;
import com.massyframework.beanskin.runtime.framework.installing.ModuleScanner;
import com.massyframework.beanskin.runtime.framework.installing.SimpleRequireLibraryIntersetor;
import com.massyframework.beanskin.runtime.framework.installing.library.LibraryInstaller;
import com.massyframework.beanskin.service.ServiceRepositoryFactory;
import com.massyframework.beanskin.service.secure.SecureCapacity;
import com.massyframework.beanskin.util.classloading.ClassLoaderUtils;
import com.massyframework.beanskin.util.classloading.LocalResourcesPath;
import com.massyframework.beanskin.util.classloading.ModuleClassLoader;
import com.massyframework.beanskin.util.classloading.ModuleClassLoaderBuilder;
import com.massyframework.beanskin.util.classloading.ResourceLocateFilterRepository;
import com.massyframework.beanskin.util.exceptioncollect.ExceptionCollectUtils;
import com.massyframework.beanskin.util.localstore.LocalStore;

/**
 * 插件安装
 * @author huangkh
 *
 */
public class PluginInstaller {
	
	private Logger logger = LoggerFactory.getLogger(LibraryInstaller.class);
	
	/**
	 * 
	 */
	public PluginInstaller() {

	}

	/**
	 * 加载并安装
	 * @param kernel {@link Assembly},装配件
	 * @param localStore {@link LocalStore},本地存储
	 */
	public List<PluginBuilder> loadingPlugins(Assembly kernel, LocalStore localStore) {
		Path[] roots = this.getRoots(localStore);
		Map<ModuleIdentifier, PluginBuilder> pluginMap = this.loadPlugins(kernel, localStore, roots);
		return new ArrayList<>(pluginMap.values());
		
		//创建有向无环图，梳理类库之间的依赖关系
		/*
		Graph<PluginBuilder> graph = GraphFactory.createGraph(PluginBuilder.class);
		List<PluginBuilder> result =  new ArrayList<>(pluginMap.values());
		pluginMap.values().forEach( builder-> graph.insert(builder));
		
		PluginBuilderComparator comparator = new PluginBuilderComparator();
		int size = result.size();
		for (int i=0; i<size; i++) {
			for (int j=1; j<size; j++) {
				PluginBuilder builder1 = result.get(i);
				PluginBuilder builder2 = result.get(j);
				
				try {
					int order = comparator.compare(builder1, builder2);
					switch (order) {
					case -1:{
						graph.insert(builder1, builder2, new Edge(1, null));
						break;
					}case 1: {
						graph.insert(builder2, builder1, new Edge(1, null));
						break;
					}
				}
				}catch(Exception e) {
					graph.remove(builder1);
					graph.remove(builder2);
					ExceptionCollectUtils.addException(
							new ModuleRuntimeException(builder1.getModuleIdentifier(), e));
				}
			}
		}
		
		result.clear();
		Predicate<Vertex<PluginBuilder>> filter = vertex -> vertex.getInDegree() == 0;
		List<PluginBuilder> tmp = graph.remove(filter);
		while (!tmp.isEmpty()) {
			result.addAll(tmp);
			tmp = graph.remove( filter);
		}

		if (graph.vertexNumber() != 0) {
			filter = vertex -> {return true; };
			tmp = graph.remove( filter);
			
			//生成例外
			for (PluginBuilder builder: tmp) {
				List<RequireLibraryDescriptor> undepends = new ArrayList<RequireLibraryDescriptor>();
				for (RequireLibraryDescriptor descriptor: builder.getRequires()) {
					ModuleIdentifier module = pluginMap.keySet().stream()
						.filter( identifier -> 
							descriptor.getGroupId().equals(identifier.getGroupId()) &&
							descriptor.getModuleId().equals(identifier.getModuleId()) &&
							descriptor.getVersionRange().includes(identifier.getVersion())
						).findFirst().orElse(null);
					if (module == null) {
						undepends.add(descriptor);
					}
				}
				StringBuilder sb = new StringBuilder();
				sb.append("plugin[")
					.append(builder.getModuleIdentifier().toIdentifier())
					.append("] cannot be install. not resolve: ");
				for (int i=0; i<undepends.size(); i++) {
					RequireLibraryDescriptor desc = undepends.get(i);
					sb.append(desc.toString());
					if (i != undepends.size() -1) {
						sb.append(",");
					}
				}
				String message = sb.toString();
				if (logger.isErrorEnabled()) {
					logger.error(message);
				}
				ExceptionCollectUtils.addException(
						new ModuleRuntimeException(builder.getModuleIdentifier(), message));
			}
			
		}
		
		
		return result;
		*/
	}
	
	/**
	 * 安装类库
	 * @param builders
	 * @param moduleJarLocator {@link ModuleJarsLocator},模块jar定位器
	 * @param moduleRepository {@link ModuleRepository},模块资源库
	 * @param serviceRepotisoryFactory {@link ServiceRepositoryFactory}, 服务资源库工厂
	 * @param framework {@link Framework},运行框架
	 * @param kernel {@link Assembly},内核装配件
	 * @param executor {@link ExecutorService}, 执行服务线程池
	 */
	public void installPlugins(List<PluginBuilder> builders, ModuleJarsLocator moduleJarsLocator, 
			ModuleRepository moduleRepository, ServiceRepositoryFactory serviceRepositoryFactory,  
			Framework framework, Assembly kernel, ExecutorService executor) {
		for (PluginBuilder builder: builders) {
			executor.execute(new Task(builder, moduleJarsLocator, 
					moduleRepository, serviceRepositoryFactory, framework, kernel));
		}

	}
	
	/**
	 * 创建插件激活器
	 * @param plugin {@link Plugin}
	 * @return {@link PluginActivator}
	 */
	protected PluginActivator createPluginActivator(Plugin plugin) throws Exception {
		String className = plugin.getConfig().getActivatorClassName();
		Class<?> clazz = plugin.getModuleClassLoader().loadClass(className);
		return PluginActivator.class.cast(clazz.newInstance());
	}
	
	/**
	 * 加载插件
	 * @param roots {@link Paths}数组
	 * @param localStore {@link LocalStore,本地存储
	 * @return {@link Map}
	 */
	protected Map<ModuleIdentifier, PluginBuilder> loadPlugins(Assembly kernel, LocalStore localStore, Path[] roots){
		ModuleScanner scanner = new ModuleScanner(roots);
		
		return scanner.scanning("plugin.xml", new PluginXmlParser(kernel, localStore));
	}
	
	/**
	 * 扫描的根目录
	 * @param localStore {@link LocalStore}
	 * @return {@link Path}数组.
	 */
	protected Path[] getRoots(LocalStore localStore) {
		return new Path[] {localStore.getDeploymentDirectory(null)};
	}

	private class Task implements Runnable {
		private PluginBuilder builder;
		private ModuleJarsLocator moduleJarsLocator;
		private ModuleRepository moduleRepository;
		private ServiceRepositoryFactory serviceRepositoryFactory;
		private Framework framework;
		private Assembly kernel;
		
		public Task(PluginBuilder builder, 
				ModuleJarsLocator moduleJarsLocator, 
				ModuleRepository moduleRepository,
				ServiceRepositoryFactory serviceRepositoryFactory,
				Framework framework, 
				Assembly kernel) {
			this.builder = builder;
			this.moduleJarsLocator = moduleJarsLocator;
			this.moduleRepository = moduleRepository;
			this.serviceRepositoryFactory = serviceRepositoryFactory;
			this.framework = framework;
			this.kernel = kernel;
		}

		@Override
		public void run() {
			ModuleRegistry moduleRegistry = (ModuleRegistry) moduleRepository;						
			//jar包
			List<URL> urls = moduleJarsLocator.locate(builder.getModuleIdentifier());
			
			SimpleRequireLibraryIntersetor intersetor = new SimpleRequireLibraryIntersetor();
			List<RequireLibraryDescriptor> list = intersetor.intersection(builder.getModuleIdentifier(), builder.getRequires(), moduleRepository);
			
			List<LocalResourcesPath> paths = new ArrayList<>();
			for (RequireLibraryDescriptor require: list) {
	            Module<?> module = moduleRepository.findModuleWithOptimal(require);
	            if (module != null) {
	                LocalResourcesPath lrp = module.getModuleClassLoader().getLocalResourcePath();
	                if (lrp != null) {
	                	paths.add(lrp);
	                }
	            }
	        }
			
			ModuleClassLoaderBuilder loaderBuilder =
					ModuleClassLoaderBuilderFactory.createModuleClassLoaderBuilder(kernel);
			SecureCapacity support = (SecureCapacity)builder;
			ResourceLocateFilterRepository filterRepo =
					kernel.getAssemblyContext().getBean(ResourceLocateFilterRepository.class.getName(), 
							ResourceLocateFilterRepository.class);
			
			ModuleClassLoader classLoader =
					loaderBuilder.publicKey(support.getPublicKey())
						.decryptClassNames(support.getDecryptClassNames())
						.decryptScriptFileNames(support.getDescryptScriptFileNames())
						.certificate(support.getX509Name(), support.getX509Encrypted())
						.parentClassLoader(Framework.class.getClassLoader())
						.moduleIdentifier(builder.getModuleIdentifier())
						.addURLs(urls)
						.addLocalResourcesPaths(paths)
						.resourceLocateFilterRepository(filterRepo)
						.build();
			
			Plugin plugin = builder.moduleRepository(moduleRepository)
					.classLoader(classLoader)
					.serviceRepositoryFactory(serviceRepositoryFactory)
					.build();
			
			moduleRegistry.register(plugin, p->{
				ClassLoader contextLoader = ClassLoaderUtils.setThreadContextClassLoader(classLoader);
				try {
					PluginActivator activator = createPluginActivator(plugin);
					DefaultEnvironment<Plugin> env = new DefaultEnvironment<Plugin>(framework, kernel, plugin);
					activator.init(env);
					return activator;
				}catch(Exception e) {
					ExceptionCollectUtils.addException(new ModuleRuntimeException(plugin, e));
										
					if (logger.isErrorEnabled()) {
						logger.error(e.getMessage(), e);
					}
				}finally {
					Thread.currentThread().setContextClassLoader(contextLoader);
				}
				
				return null;
			});
		}		
	}
}
